package com.szgz.platform.service.impl;

import com.szgz.platform.mapper.UserMapper;
import com.szgz.platform.mapper.UserEnvironmentMapper;
import com.szgz.platform.mapper.UserResourceMapper;
import com.szgz.platform.mapper.SharedIDEEnvironmentMapper;
import com.szgz.platform.model.User;
import com.szgz.platform.model.UserEnvironment;
import com.szgz.platform.model.UserResource;
import com.szgz.platform.model.SharedIDEEnvironment;
import com.szgz.platform.service.UserManagementService;
import com.szgz.platform.service.SharedContainerService;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

/**
 * 用户管理服务实现类
 */
@Service
public class UserManagementServiceImpl implements UserManagementService {

    private static final Logger logger = LoggerFactory.getLogger(UserManagementServiceImpl.class);

    // 注入Mapper接口
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserEnvironmentMapper userEnvironmentMapper;

    @Autowired
    private UserResourceMapper userResourceMapper;

    @Autowired
    private SharedIDEEnvironmentMapper sharedIDEEnvironmentMapper;

    // 共享容器通用能力
    @Autowired
    private SharedContainerService sharedContainerService;

    private static final String BASE_URL = "http://192.168.1.168";
    private static final String USERS_DIR = "./users";
    private static final String SHARED_DIR = "./shared";

    public UserManagementServiceImpl() {
        initializeDirectories();
    }

    // 解析 Docker 可执行路径，兼容不同操作系统/环境
    // Docker 命令与路径解析委托给 SharedContainerService

    @Override
    public User createUser(User user) {
        logger.info("Creating user: {}", user.getUserId());

        if (userMapper.selectById(user.getUserId()) != null) {
            throw new RuntimeException("User already exists: " + user.getUserId());
        }

        // 资源限制现在通过UserResource表单独管理，不再存储在User表中
        userMapper.insert(user);

        // 创建用户的默认资源限制记录
        UserResource resourceLimits = getDefaultResourceLimits(user.getRole());
        resourceLimits.setUserId(user.getUserId());
        userResourceMapper.insert(resourceLimits);

        logger.info("User created: {}", user.getUserId());
        return user;
    }

    @Override
    public User createRandomUser(String role) {
        String userId = generateRandomUserId();
        String username = generateRandomUsername();
        String email = generateRandomEmail(username);
        String password = generateRandomPassword();

        User user = new User(userId, username, email, role);
        user.setPassword(password);

        return createUser(user);
    }

    @Override
    public List<User> createRandomUsersBatch(int count, String role) {
        List<User> createdUsers = new ArrayList<>();

        for (int i = 0; i < count; i++) {
            try {
                User user = createRandomUser(role);
                createdUsers.add(user);
            } catch (Exception e) {
                logger.error("Failed to create random user in batch: {}", e.getMessage());
            }
        }

        logger.info("Created {} random users with role: {}", createdUsers.size(), role);
        return createdUsers;
    }

    /**
     * 创建共享 IDE 环境（单容器多进程，多用户目录隔离）
     */
    @Override
    public Map<String, Object> createSharedIDEEnvironment(String groupId, List<String> studentIds) {
        Objects.requireNonNull(groupId, "groupId");
        Objects.requireNonNull(studentIds, "studentIds");

        try {
            String groupDir = SHARED_DIR + "/" + groupId;
            Files.createDirectories(Paths.get(groupDir));

            // 为每个学生分配端口
            Map<String, Integer> studentToPort = new HashMap<>();
            for (String sid : studentIds) {
                if (sid == null || sid.isBlank()) continue;
                studentToPort.put(sid, sharedContainerService.allocatePort());
            }

            String compose = generateSharedIdeCompose(groupId, new ArrayList<>(studentToPort.values()));
            Files.write(Paths.get(groupDir + "/docker-compose.yml"), compose.getBytes());

            ProcessBuilder up = sharedContainerService.pbDocker("compose", "up", "-d");
            up.directory(new File(groupDir));
            Process upProc = up.start();
            upProc.waitFor();

            // 等待容器就绪，避免立即 exec 失败 - IDE容器需要更长时间启动
            waitForContainerReady("ide-shared-" + groupId, 120);
            // 下发公共启动脚本
            ensureSharedLauncher("ide-shared-" + groupId);

            Map<String, String> studentToUrl = new HashMap<>();
            for (String sid : studentIds) {
                if (sid == null || sid.isBlank()) continue;
                // 不再生成token，确保完全无密码访问
                int port = studentToPort.getOrDefault(sid, -1);
                setupSharedIdeUser(groupId, sid, "", port);  // 传递空字符串作为token
                studentToUrl.put(sid, BASE_URL + ":" + port + "?user=" + sid);  // URL中不包含token
            }

            // 持久化端口映射
            persistSharedPorts(groupDir, studentToPort);

            // 保存到数据库
            SharedIDEEnvironment sharedEnv = new SharedIDEEnvironment(groupId, groupId, "admin");
            sharedEnv.setStudentIds(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(studentIds));
            sharedEnv.setPortMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(studentToPort));
            sharedEnv.setUrlMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(studentToUrl));
            sharedEnv.setContainerName("ide-shared-" + groupId);
            sharedEnv.setStatus("RUNNING");
            sharedEnv.setLastStarted(LocalDateTime.now());
            
            sharedIDEEnvironmentMapper.insert(sharedEnv);

            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("groupId", groupId);
            result.put("studentUrls", studentToUrl);
            result.put("ports", studentToPort);
            result.put("environmentId", sharedEnv.getEnvironmentId());
            return result;
        } catch (Exception e) {
            logger.error("createSharedIDEEnvironment failed", e);
            throw new RuntimeException("createSharedIDEEnvironment failed: " + e.getMessage(), e);
        }
    }

    private String generateSharedIdeCompose(String groupId, List<Integer> ports) {
        return new StringBuilder()
            .append("services:\n")
            .append("  ide-shared-").append(groupId).append(":\n")
            .append("    image: crpi-ycy45s92ut53ifd4.cn-hangzhou.personal.cr.aliyuncs.com/boogie0095/code-server-rebuild:latest\n")
            .append("    container_name: ide-shared-").append(groupId).append("\n")
            .append("    ports:\n")
            .append(buildPortsSection(ports))
            .append("    environment:\n")
            .append("      # 禁用密码认证，实现无密码登录\n")
            .append("      PASSWORD: \"\"\n")
            .append("      AUTH: none\n")
            .append("      # 禁用认证相关配置\n")
            .append("      DISABLE_AUTH: true\n")
            .append("      SKIP_AUTH: true\n")
            .append("      SHARED_MODE: true\n")
            .append("    volumes:\n")
            .append("      - shared-").append(groupId).append("-workspace:/workspace\n")
            .append("      - shared-").append(groupId).append("-config:/home/coder/.config\n")
            .append("    restart: unless-stopped\n")
            .append("    healthcheck:\n")
            .append("      test: [\"CMD\", \"sh\", \"-c\", \"ls /usr/local/bin/start-code.sh >/dev/null 2>&1 && id -u >/dev/null 2>&1\"]\n")
            .append("      interval: 10s\n")
            .append("      timeout: 5s\n")
            .append("      retries: 12\n")
            .append("      start_period: 60s\n")
            .append("    networks:\n")
            .append("      - shared-").append(groupId).append("-network\n\n")
            .append("volumes:\n")
            .append("  shared-").append(groupId).append("-workspace:\n")
            .append("    driver: local\n")
            .append("  shared-").append(groupId).append("-config:\n")
            .append("    driver: local\n\n")
            .append("networks:\n")
            .append("  shared-").append(groupId).append("-network:\n")
            .append("    driver: bridge\n")
            .toString();
    }

    private String buildPortsSection(List<Integer> ports) {
        StringBuilder sb = new StringBuilder();
        for (Integer p : ports) {
            sb.append("      - \"").append(p).append(":").append(p).append("\"\n");
        }
        return sb.toString();
    }

    private void waitForContainerReady(String containerName, int timeoutSeconds) throws Exception {
        sharedContainerService.waitForContainerReady(containerName, timeoutSeconds);
    }

    private void setupSharedIdeUser(String groupId, String studentId, String token, int port) throws Exception {
        String container = "ide-shared-" + groupId;
        
        // 分步执行，确保每个步骤都成功
        // 1) 创建用户
        String createUserScript = String.join(" ",
            // 已存在则跳过；仅创建用户，不设置密码，后续以 root 切换身份运行进程
            "id ", studentId, " >/dev/null 2>&1 || useradd -m -s /bin/bash ", studentId
        );
        logger.info("[shared-ide:{}] create user {}", groupId, studentId);
        ProcessBuilder createUser = sharedContainerService.pbDocker("exec", "-u", "0", container, "sh", "-c", createUserScript);
        Process createUserProc = createUser.start();
        int createUserExit = createUserProc.waitFor();
        if (createUserExit != 0) {
            String err = new String(createUserProc.getErrorStream().readAllBytes());
            logger.error("[shared-ide:{}] create user {} failed, exit={}, stderr={}", groupId, studentId, createUserExit, err);
            throw new RuntimeException("Failed to create user " + studentId + ", exit=" + createUserExit + ", stderr=" + err);
        }
        
        // 2) 创建并设置工作目录
        String setupDirScript = String.join(" ",
            // 确保父目录可遍历
            "chmod 755 /workspace || true;",
            "mkdir -p /workspace/" + studentId + ";",
            "chown -R " + studentId + ":" + studentId + " /workspace/" + studentId + ";",
            "chmod 700 /workspace/" + studentId + ";",
            // 准备每位学生的 HOME 配置目录，避免默认写入 /root/.config
            "mkdir -p /home/" + studentId + "/.config /home/" + studentId + "/.local/share;",
            "chown -R " + studentId + ":" + studentId + " /home/" + studentId + "/.config /home/" + studentId + "/.local"
        );
        logger.info("[shared-ide:{}] prepare workspace for {}", groupId, studentId);
        ProcessBuilder setupDir = sharedContainerService.pbDocker("exec", "-u", "0", container, "sh", "-c", setupDirScript);
        Process setupDirProc = setupDir.start();
        int setupDirExit = setupDirProc.waitFor();
        if (setupDirExit != 0) {
            String err = new String(setupDirProc.getErrorStream().readAllBytes());
            logger.error("[shared-ide:{}] prepare workspace for {} failed, exit={}, stderr={}", groupId, studentId, setupDirExit, err);
            throw new RuntimeException("Failed to setup directory for " + studentId + ", exit=" + setupDirExit + ", stderr=" + err);
        }
        
        // 3) 检测 code-server 路径
        String checkCodeServerScript = "command -v code-server || which code-server || echo /usr/bin/code-server";
        logger.info("[shared-ide:{}] detect code-server path for {}", groupId, studentId);
        ProcessBuilder checkCodeServer = sharedContainerService.pbDocker("exec", container, "sh", "-c", checkCodeServerScript);
        Process checkCodeServerProc = checkCodeServer.start();
        String codeServerPath = new String(checkCodeServerProc.getInputStream().readAllBytes()).trim();
        checkCodeServerProc.waitFor();
        
        if (codeServerPath.isEmpty() || codeServerPath.equals("/usr/bin/code-server")) {
            // 验证默认路径是否可执行
            ProcessBuilder verifyPath = sharedContainerService.pbDocker("exec", container, "test", "-x", "/usr/bin/code-server");
            Process verifyPathProc = verifyPath.start();
            int verifyPathExit = verifyPathProc.waitFor();
            if (verifyPathExit != 0) {
                throw new RuntimeException("code-server not found or not executable in container");
            }
            codeServerPath = "/usr/bin/code-server";
        }
        
        // 4) 停止可能占用端口的进程
        String killProcessScript = "pkill -f 'code-server.*--bind-addr.*:" + port + "' || true";
        logger.info("[shared-ide:{}] cleanup old process for {} on port {}", groupId, studentId, port);
        ProcessBuilder killProcess = sharedContainerService.pbDocker("exec", "-u", "0", container, "sh", "-c", killProcessScript);
        Process killProcessProc = killProcess.start();
        killProcessProc.waitFor(); // 忽略退出码，因为可能没有进程需要杀死
        
        // 5) 以学生身份直接执行公共启动脚本，避免多层 shell 转义/回退 8080
        logger.info("[shared-ide:{}] exec shared launcher for {} on {}", groupId, studentId, port);
        ProcessBuilder startCodeServer = new ProcessBuilder(
            "/usr/bin/docker", "exec", "-u", studentId, "-d", container,
            "/usr/local/bin/start-code.sh", studentId, String.valueOf(port), ""  // 传递空字符串而不是token，确保无密码访问
        );
        Process startCodeServerProc = startCodeServer.start();
        int startCodeServerExit = startCodeServerProc.waitFor();
        if (startCodeServerExit != 0) {
            String err = new String(startCodeServerProc.getErrorStream().readAllBytes());
            logger.error("[shared-ide:{}] launcher exec for {} failed, exit={}, stderr={}", groupId, studentId, startCodeServerExit, err);
            throw new RuntimeException("Failed to start code-server for " + studentId + ", exit=" + startCodeServerExit + ", stderr=" + err);
        }
        
        // 6) 轮询等待端口监听（最多 30 秒）
        String verifyProcessScript = "(netstat -tlnp 2>/dev/null | grep -q :" + port + ") || (ss -lntp 2>/dev/null | grep -q :" + port + ")";
        boolean ready = false;
        for (int i = 0; i < 30; i++) {
            ProcessBuilder verifyProcess = sharedContainerService.pbDocker("exec", container, "sh", "-lc", verifyProcessScript);
            Process verifyProcessProc = verifyProcess.start();
            int verifyProcessExit = verifyProcessProc.waitFor();
            if (verifyProcessExit == 0) { ready = true; break; }
            Thread.sleep(1000);
        }
        if (!ready) {
            String lastLog = "";
            try {
                ProcessBuilder tailLog = sharedContainerService.pbDocker("exec", container, "sh", "-lc", "tail -n 200 \"/home/" + studentId + "/.local/share/logs/code-server.log\" 2>/dev/null || true");
                Process t = tailLog.start();
                lastLog = new String(t.getInputStream().readAllBytes());
                t.waitFor();
            } catch (Exception ignore) {}
            logger.error("[shared-ide:{}] port {} not ready for {} after wait, tailLog=\n{}", groupId, port, studentId, lastLog);
            throw new RuntimeException("code-server process verification failed for " + studentId + " on port " + port);
        }
    }

    private void persistSharedPorts(String groupDir, Map<String, Integer> mapping) {
        sharedContainerService.persistPorts(groupDir, mapping);
    }

    private Map<String, Integer> readSharedPorts(String groupId) {
        return sharedContainerService.readPortsByGroupId(groupId);
    }

    @Override
    public Map<String, Object> deleteSharedIDEEnvironment(String groupId) {
        Objects.requireNonNull(groupId, "groupId");
        String groupDir = SHARED_DIR + "/" + groupId;
        try {
            // docker compose down -v
            ProcessBuilder down = sharedContainerService.pbDocker("compose", "down", "-v");
            down.directory(new File(groupDir));
            Process proc = down.start();
            proc.waitFor();

            // 删除目录
            deleteDirectory(new File(groupDir));

            // 从数据库中删除记录
            SharedIDEEnvironment existingEnv = sharedIDEEnvironmentMapper.selectByGroupId(groupId);
            if (existingEnv != null) {
                sharedIDEEnvironmentMapper.deleteById(existingEnv.getEnvironmentId());
            }

            Map<String, Object> res = new HashMap<>();
            res.put("success", true);
            res.put("message", "Shared IDE environment deleted: " + groupId);
            return res;
        } catch (Exception e) {
            logger.error("deleteSharedIDEEnvironment failed", e);
            throw new RuntimeException("deleteSharedIDEEnvironment failed: " + e.getMessage(), e);
        }
    }

    @Override
    public Map<String, Integer> getSharedIDEPorts(String groupId) {
        return readSharedPorts(groupId);
    }

    @Override
    public String generateSharedIDENginxConfig(String groupId, String serverName, String basePath) {
        if (basePath == null || basePath.isBlank()) basePath = "/";
        if (!basePath.startsWith("/")) basePath = "/" + basePath;
        if (basePath.endsWith("/")) basePath = basePath.substring(0, basePath.length() - 1);

        Map<String, Integer> mapping = readSharedPorts(groupId);
        if (mapping.isEmpty()) {
            throw new RuntimeException("No port mapping found for group: " + groupId);
        }

        StringBuilder sb = new StringBuilder();
        sb.append("server {\n");
        sb.append("    listen 80;\n");
        sb.append("    server_name ").append(serverName).append(";\n\n");

        for (Map.Entry<String, Integer> e : mapping.entrySet()) {
            String sid = e.getKey();
            int port = e.getValue();
            String loc = basePath + "/" + groupId + "/" + sid;
            sb.append("    location ").append(loc).append("/ {\n");
            sb.append("        proxy_http_version 1.1;\n");
            sb.append("        proxy_set_header Upgrade $http_upgrade;\n");
            sb.append("        proxy_set_header Connection \"upgrade\";\n");
            sb.append("        proxy_set_header Host $host;\n");
            sb.append("        proxy_set_header X-Real-IP $remote_addr;\n");
            sb.append("        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n");
            sb.append("        proxy_set_header X-Forwarded-Proto $scheme;\n");
            sb.append("        proxy_read_timeout 3600s;\n");
            sb.append("        proxy_send_timeout 3600s;\n");
            sb.append("        proxy_pass http://127.0.0.1:").append(port).append("/;\n");
            sb.append("    }\n\n");
        }

        // 可选：默认重定向到说明页
        sb.append("    location = ").append(basePath).append("/"+groupId+") { return 302 ")
          .append(basePath).append("/").append(groupId).append("/; }\n");

        sb.append("}\n");
        return sb.toString();
    }

    @Override
    public java.util.Map<String, Object> createSharedJupyterHubEnvironment(String groupId, java.util.List<String> studentIds) {
        Objects.requireNonNull(groupId, "groupId");
        Objects.requireNonNull(studentIds, "studentIds");
        try {
            String groupDir = SHARED_DIR + "/" + groupId + "-jhub";
            Files.createDirectories(Paths.get(groupDir));

            int hubPort = sharedContainerService.allocatePort();
            // 生成 Hub API Token（用于平台批量创建用户）
            String apiToken = java.util.UUID.randomUUID().toString().replace("-", "");

            // 写入 docker-compose.yml
            String compose = new StringBuilder()
                .append("services:\n")
                .append("  jhub-").append(groupId).append(":\n")
                .append("    image: jupyterhub/jupyterhub:latest\n")
                .append("    container_name: jhub-").append(groupId).append("\n")
                .append("    ports:\n")
                .append("      - \"").append(hubPort).append(":8000\"\n")
                .append("    volumes:\n")
                .append("      - ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py\n")
                .append("      - /var/run/docker.sock:/var/run/docker.sock\n")
                .append("      - hub-data:/srv/jupyterhub\n")
                .append("    command: bash -c \"python3 -m pip install --no-cache-dir dockerspawner && jupyterhub -f /srv/jupyterhub/jupyterhub_config.py\"\n")
                .append("    networks:\n")
                .append("      - jhub-").append(groupId).append("-net\n")
                .append("    restart: unless-stopped\n\n")
                .append("volumes:\n")
                .append("  hub-data:\n")
                .append("    driver: local\n\n")
                .append("networks:\n")
                .append("  jhub-").append(groupId).append("-net:\n")
                .append("    name: jhub-").append(groupId).append("-net\n")
                .append("    driver: bridge\n")
                .toString();
            Files.write(Paths.get(groupDir, "docker-compose.yml"), compose.getBytes());

            // 写入 jupyterhub_config.py
            String allowed = studentIds.stream().filter(s -> s != null && !s.isBlank())
                .map(s -> "'" + s.replace("'", "") + "'")
                .collect(java.util.stream.Collectors.joining(", "));
            String config = String.join("\n",
                "c = get_config()",
                "c.JupyterHub.bind_url = 'http://:8000'",
                "c.JupyterHub.base_url = '/jhub/'",
                "c.Spawner.default_url = '/lab'",
                "# Hub 默认 8000 端口，容器间通过网络名访问 jhub-{groupId}:8000",
                "c.JupyterHub.hub_ip = '0.0.0.0'",
                "c.JupyterHub.hub_connect_ip = 'jhub-" + groupId + "'",
                "# 使用默认 8000，无需额外 hub_connect_url",
                "# Spawner 配置",
                "from dockerspawner import DockerSpawner",
                "c.JupyterHub.spawner_class = DockerSpawner",
                "c.DockerSpawner.image = 'jupyterhub/singleuser:latest'",
                "c.DockerSpawner.network_name = 'jhub-" + groupId + "-net'",
                "c.DockerSpawner.use_internal_ip = True",
                "c.DockerSpawner.notebook_dir = '/home/jovyan/work'",
                "c.DockerSpawner.remove = True",
                "c.DockerSpawner.volumes = { 'jhub-{username}': '/home/jovyan/work' }",
                "# 中文语言环境配置",
                "c.DockerSpawner.environment = {",
                "    'LANG': 'zh_CN.UTF-8',",
                "    'LC_ALL': 'zh_CN.UTF-8',",
                "    'LANGUAGE': 'zh_CN:zh',",
                "    'JUPYTER_ENABLE_LAB': 'yes'",
                "}",
                "# 启动/健康检查超时",
                "c.Spawner.start_timeout = 180",
                "c.Spawner.http_timeout = 180",
                "# 认证（演示用）",
                "from jupyterhub.auth import DummyAuthenticator",
                "c.JupyterHub.authenticator_class = DummyAuthenticator",
                "c.DummyAuthenticator.password = 'pass'",
                "c.Authenticator.allowed_users = {" + allowed + "}",
                "# 平台服务令牌（用于后续API调用）",
                "c.JupyterHub.services = [ { 'name': 'platform', 'api_token': '" + apiToken + "' } ]"
            );
            Files.write(Paths.get(groupDir, "jupyterhub_config.py"), config.getBytes(StandardCharsets.UTF_8));

            // 启动 Hub
            ProcessBuilder up = sharedContainerService.pbDocker("compose", "up", "-d");
            up.directory(new File(groupDir));
            Process upProc = up.start();
            upProc.waitFor();

            String containerName = "jhub-" + groupId;
            waitForContainerReady(containerName, 45);

            // 通过 Hub API 批量创建用户（DummyAuthenticator 可选，但先预创建便于管理）
            String hubBase = "http://127.0.0.1:" + hubPort;
            // 等待 Hub /hub/health 就绪（最多 60s）
            for (int i = 0; i < 60; i++) {
                try {
                    URL u = new URL(hubBase + "/hub/health");
                    HttpURLConnection c = (HttpURLConnection) u.openConnection();
                    c.setConnectTimeout(1000);
                    c.setReadTimeout(1000);
                    c.connect();
                    int code = c.getResponseCode();
                    c.disconnect();
                    if (code == 200) { break; }
                } catch (Exception ignore) {}
                Thread.sleep(1000);
            }

            for (String sid : studentIds) {
                if (sid == null || sid.isBlank()) continue;
                try {
                    hubApiCreateUser(hubBase, apiToken, sid.trim());
                } catch (Exception ex) {
                    logger.warn("Failed to pre-create Hub user {}: {}", sid, ex.getMessage());
                }
            }

            Map<String, String> userToUrl = new HashMap<>();
            for (String sid : studentIds) {
                if (sid == null || sid.isBlank()) continue;
                userToUrl.put(sid, BASE_URL + ":" + hubPort + "/jhub/hub/login");
            }

            SharedIDEEnvironment sharedEnv = new SharedIDEEnvironment(groupId, groupId + "-jhub", "admin");
            sharedEnv.setStudentIds(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(studentIds));
            sharedEnv.setPortMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(java.util.Collections.singletonMap("hub", hubPort)));
            sharedEnv.setUrlMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(userToUrl));
            sharedEnv.setContainerName(containerName);
            sharedEnv.setStatus("RUNNING");
            sharedEnv.setLastStarted(LocalDateTime.now());
            sharedIDEEnvironmentMapper.insert(sharedEnv);

            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("groupId", groupId);
            result.put("hubPort", hubPort);
            result.put("loginUrls", userToUrl);
            result.put("environmentId", sharedEnv.getEnvironmentId());
            return result;
        } catch (Exception e) {
            logger.error("createSharedJupyterHubEnvironment failed", e);
            throw new RuntimeException("createSharedJupyterHubEnvironment failed: " + e.getMessage(), e);
        }
    }

    @Override
    public java.util.Map<String, Object> deleteSharedJupyterHubEnvironment(String groupId) {
        Objects.requireNonNull(groupId, "groupId");
        String groupDir = SHARED_DIR + "/" + groupId + "-jhub";
        try {
            ProcessBuilder down = sharedContainerService.pbDocker("compose", "down", "-v");
            down.directory(new File(groupDir));
            Process proc = down.start();
            proc.waitFor();

            deleteDirectory(new File(groupDir));

            SharedIDEEnvironment env = sharedIDEEnvironmentMapper.selectByGroupId(groupId);
            if (env != null && env.getContainerName() != null && env.getContainerName().startsWith("jhub-")) {
                sharedIDEEnvironmentMapper.deleteById(env.getEnvironmentId());
            }

            Map<String, Object> res = new HashMap<>();
            res.put("success", true);
            res.put("message", "Shared JupyterHub environment deleted: " + groupId);
            return res;
        } catch (Exception e) {
            logger.error("deleteSharedJupyterHubEnvironment failed", e);
            throw new RuntimeException("deleteSharedJupyterHubEnvironment failed: " + e.getMessage(), e);
        }
    }

    @Override
    public Map<String, Object> deleteSharedEnvironment(String groupId) {
        Objects.requireNonNull(groupId, "groupId");
        Map<String, Object> res = new HashMap<>();
        res.put("groupId", groupId);
        // 先尝试 JupyterHub
        try {
            Map<String, Object> r1 = deleteSharedJupyterHubEnvironment(groupId);
            if (Boolean.TRUE.equals(r1.get("success"))) {
                res.putAll(r1);
                res.put("message", "Deleted JupyterHub shared environment");
                res.put("type", "JUPYTERHUB");
                return res;
            }
        } catch (Exception ignore) {}
        // 再尝试 IDE
        try {
            Map<String, Object> r2 = deleteSharedIDEEnvironment(groupId);
            if (Boolean.TRUE.equals(r2.get("success"))) {
                res.putAll(r2);
                res.put("message", "Deleted IDE shared environment");
                res.put("type", "IDE");
                return res;
            }
        } catch (Exception ignore) {}

        res.put("success", false);
        res.put("message", "No shared environment found for groupId: " + groupId);
        return res;
    }

    private void hubApiCreateUser(String hubBase, String apiToken, String username) throws Exception {
        String url = hubBase + "/jhub/hub/api/users/" + java.net.URLEncoder.encode(username, java.nio.charset.StandardCharsets.UTF_8);
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Authorization", "token " + apiToken);
        conn.setDoOutput(true);
        conn.getOutputStream().write(new byte[0]);
        int code = conn.getResponseCode();
        if (code >= 400) {
            throw new RuntimeException("Hub API create user failed, code=" + code);
        }
        conn.disconnect();
    }

    @Override
    public UserEnvironment createUserEnvironment(String userId, String userRole) {
        return createUserEnvironment(userId, userRole, Set.of(UserEnvironment.EnvironmentType.IDE, UserEnvironment.EnvironmentType.JUPYTER));
    }

    @Override
    public UserEnvironment createUserEnvironment(String userId, String userRole, Set<UserEnvironment.EnvironmentType> environmentTypes) {
        logger.info("开始创建 environment for user: {} with types: {}", userId, environmentTypes);

        if (userMapper.selectById(userId) == null) {
            throw new RuntimeException("User not found: " + userId);
        }
        boolean inMemoryExists = false;
        // 如果内存中不存在环境，也尝试基于现有容器或磁盘上的 compose 来进行合并扩容
        UserEnvironment userEnvironment = userEnvironmentMapper.selectByUserIdSingle(userId);
        if (userEnvironment != null) {

            //判断environmentTypes的第一个是否存在于userEnvironment.getEnvironmentTypes()中
            inMemoryExists = environmentTypes.stream().anyMatch(userEnvironment::hasEnvironmentType);
        }

        if (inMemoryExists) {
            // 如果内存中存在环境，返回环境已存在，无需重复创建
            logger.info("检测到现有环境，删除旧环境并重新创建，用户: {}", userId);
            return new UserEnvironment(userId, environmentTypes);
        }

        // 内存中不存在：仅使用请求的环境类型，不检测现有容器
        logger.info("创建新环境，用户: {}, 请求类型: {}", userId, environmentTypes);

        java.util.Set<UserEnvironment.EnvironmentType> mergedTypes = new java.util.HashSet<>(environmentTypes);
        logger.info("使用请求的环境类型: {}", mergedTypes);

        java.util.Map<String, Integer> ports = new java.util.HashMap<>();
        // 为新环境分配端口，不检测现有容器
        logger.info("为新环境分配端口");

        // 为缺失服务分配端口
        logger.info("为缺失服务分配端口");
        allocateMissingPorts(mergedTypes, ports);
        logger.info("最终端口分配: {}", ports);

        logger.info("创建UserEnvironment对象，用户: {}, 端口: {}, 类型: {}", userId, ports, mergedTypes);

        UserEnvironment environment = userEnvironment == null ? new UserEnvironment(userId, ports, mergedTypes) : userEnvironment;
        logger.info("UserEnvironment对象创建完成: {}", environment);

        logger.info("设置URL信息");
        setUrlsFromPorts(environment, ports);

        try {
            logger.info("开始创建Docker环境，用户: {}, 端口: {}, 类型: {}", userId, ports, mergedTypes);
            createDockerEnvironment(userId, ports, mergedTypes);
            logger.info("Docker环境创建完成，用户: {}", userId);

            // 把新建的环境mergedTypes 放入 environment.getEnvironmentTypes()中
            Set<UserEnvironment.EnvironmentType> typeSet = environment.getEnvironmentTypes();
            typeSet.addAll(mergedTypes);
            environment.setEnvironmentTypes(typeSet);
            environment.setStatus("RUNNING");
            environment.setLastStarted(java.time.LocalDateTime.now());
            logger.info("环境状态已更新为RUNNING，用户: {}", userId);
        } catch (Exception e) {
            logger.error("Failed to create Docker environment for user: {}", userId, e);
            environment.setStatus("ERROR");
            throw new RuntimeException("Failed to create environment", e);
        }

        logger.info("准备插入环境到数据库: {}", environment);
        logger.info("环境对象详情 - userId: {}, status: {}, environmentTypes: {}",
                environment.getUserId(), environment.getStatus(), environment.getEnvironmentTypes());
        logger.info("对象json数据：{}", environment);
        try {
            if (environment.getEnvironmentId() != null) {
                int insertResult = userEnvironmentMapper.updateById(environment);
                logger.info("数据库更新结果: {} 行受影响, 用户: {}", insertResult, userId);
            } else {
                int insertResult = userEnvironmentMapper.insert(environment);
                logger.info("数据库插入结果: {} 行受影响, 用户: {}", insertResult, userId);
                // 插入成功后，获取自动生成的ID
                if (insertResult > 0 && environment.getEnvironmentId() == null) {
                    // 重新查询获取完整的对象信息
                    UserEnvironment savedEnvironment = userEnvironmentMapper.selectByUserIdSingle(userId);
                    if (savedEnvironment != null) {
                        environment.setEnvironmentId(savedEnvironment.getEnvironmentId());
                    }
                }
            }

        } catch (Exception e) {
            logger.error("数据库插入异常，用户: {}, 错误: {}", userId, e.getMessage(), e);
        }

        createDatabaseUser(userId);

        logger.info("Environment created for user: {} -> {}", userId, mergedTypes);
        return environment;
    }

    @Override
    public boolean startUserEnvironment(String userId) {
        logger.info("Starting environment for user: {}", userId);

        UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
        if (environment == null) {
            return false;
        }

        try {
            String userDir = USERS_DIR + "/" + userId;

            // 检查容器是否已经运行且有端口映射
            boolean containerRunning = false;
            boolean hasPortMapping = false;

            try {
                // 检查容器状态
                ProcessBuilder checkPb = new ProcessBuilder("/usr/bin/docker", "ps", "--format", "{{.Names}}\t{{.Ports}}");
                Process checkProcess = checkPb.start();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(checkProcess.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        if (line.contains("ide-" + userId)) {
                            containerRunning = true;
                            if (line.contains("->")) {
                                hasPortMapping = true;
                            }
                            break;
                        }
                    }
                }
                checkProcess.waitFor();
            } catch (Exception e) {
                logger.warn("Error checking container status: {}", e.getMessage());
            }

            // 如果容器运行但没有端口映射，先停止它
            if (containerRunning && !hasPortMapping) {
                logger.info("Container is running but has no port mapping, stopping it first");
                ProcessBuilder stopPb = new ProcessBuilder("/usr/bin/docker", "compose", "down");
                stopPb.directory(new File(userDir));
                Process stopProcess = stopPb.start();
                stopProcess.waitFor();
                Thread.sleep(2000); // 等待容器完全停止
            }

            // 启动环境
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "compose", "up", "-d");
            pb.directory(new File(userDir));
            pb.redirectErrorStream(true);
            Process process = pb.start();

            // 读取输出
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
            }

            int exitCode = process.waitFor();
            if (exitCode == 0) {
                // 等待容器启动
                Thread.sleep(5000);

                // 验证容器状态
                boolean success = verifyContainerStatus(userId);
                if (success) {
                    environment.setStatus("RUNNING");
                    environment.setLastStarted(LocalDateTime.now());
                    logger.info("Environment started successfully for user: {}", userId);
                    return true;
                } else {
                    logger.error("Container verification failed for user: {}", userId);
                    return false;
                }
            } else {
                logger.error("Failed to start environment for user {}: {}", userId, output.toString());
                return false;
            }
        } catch (Exception e) {
            logger.error("Error starting environment for user: {}", userId, e);
            return false;
        }
    }

    @Override
    public boolean stopUserEnvironment(String userId) {
        logger.info("Stopping environment for user: {}", userId);

        UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
        if (environment == null) {
            return false;
        }

        try {
            String userDir = USERS_DIR + "/" + userId;
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "compose", "down");
            pb.directory(new File(userDir));
            pb.redirectErrorStream(true);
            Process process = pb.start();

            // 读取输出
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
            }

            int exitCode = process.waitFor();
            if (exitCode == 0) {
                environment.setStatus("STOPPED");
                logger.info("Environment stopped successfully for user: {}", userId);
                return true;
            } else {
                logger.error("Failed to stop environment for user {}: {}", userId, output.toString());
                return false;
            }
        } catch (Exception e) {
            logger.error("Error stopping environment for user: {}", userId, e);
            return false;
        }
    }

    @Override
    public boolean deleteUserEnvironment(String userId) {
        logger.info("Deleting environment for user: {}", userId);

        stopUserEnvironment(userId);

        try {
            String userDir = USERS_DIR + "/" + userId;
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "compose", "down", "-v");
            pb.directory(new File(userDir));
            Process process = pb.start();
            process.waitFor();

            deleteDirectory(new File(userDir));
            deleteDatabaseUser(userId);

            UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
            if (environment != null) {
                // 安全地释放端口，检查空值
                if (environment.getIdePort() != null) {
                    releasePort(environment.getIdePort());
                }
                if (environment.getJupyterPort() != null) {
                    releasePort(environment.getJupyterPort());
                }
                if (environment.getRedisPort() != null) {
                    releasePort(environment.getRedisPort());
                }
                if (environment.getMysqlPort() != null) {
                    releasePort(environment.getMysqlPort());
                }
                if (environment.getRabbitmqPort() != null) {
                    releasePort(environment.getRabbitmqPort());
                }
                if (environment.getElasticsearchPort() != null) {
                    releasePort(environment.getElasticsearchPort());
                }
                if (environment.getVirtualDesktopPort() != null) {
                    releasePort(environment.getVirtualDesktopPort());
                }
            }

            userEnvironmentMapper.deleteByUserId(userId);
            userResourceMapper.deleteById(userId);

            return true;
        } catch (Exception e) {
            logger.error("Error deleting environment for user: {}", userId, e);
            return false;
        }
    }

    @Override
    public UserEnvironment getUserEnvironmentStatus(String userId) {
        return userEnvironmentMapper.selectByUserIdSingle(userId);
    }

    @Override
    public UserResource getUserResourceUsage(String userId) {
        UserResource resource = userResourceMapper.selectById(userId);
        if (resource == null) {
            resource = new UserResource();
            resource.setUserId(userId);
            userResourceMapper.insert(resource);
        }

        // 模拟资源使用数据
        resource.setCurrentCpuUsage((int) (Math.random() * 50));
        resource.setCurrentMemoryUsage((int) (Math.random() * 1000 + 500));
        resource.setCurrentDiskUsage((int) (Math.random() * 5 + 1));
        resource.setCurrentNetworkUsage((int) (Math.random() * 5));
        resource.setUpdatedAt(LocalDateTime.now());

        return resource;
    }

    @Override
    public String backupUserData(String userId) {
        logger.info("Backing up data for user: {}", userId);

        try {
            String backupDir = "./backups/" + userId;
            Files.createDirectories(Paths.get(backupDir));

            String timestamp = LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
            String backupPath = backupDir + "/backup_" + timestamp + ".tar.gz";

            UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
            if (environment != null && "RUNNING".equals(environment.getStatus())) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "ide-" + userId,
                        "tar", "-czf", "/tmp/user-data.tar.gz", "/home/coder/project");
                Process process = pb.start();
                process.waitFor();

                pb = new ProcessBuilder("/usr/bin/docker", "cp", "ide-" + userId + ":/tmp/user-data.tar.gz", backupPath);
                process = pb.start();
                process.waitFor();
            }

            return backupPath;
        } catch (Exception e) {
            logger.error("Error backing up data for user: {}", userId, e);
            throw new RuntimeException("Backup failed", e);
        }
    }

    @Override
    public boolean restoreUserData(String userId, String backupPath) {
        logger.info("Restoring data for user: {}", userId);

        try {
            UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
            if (environment != null && "RUNNING".equals(environment.getStatus())) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "cp", backupPath, "ide-" + userId + ":/tmp/");
                Process process = pb.start();
                process.waitFor();

                pb = new ProcessBuilder("/usr/bin/docker", "exec", "ide-" + userId,
                        "tar", "-xzf", "/tmp/user-data.tar.gz", "-C", "/");
                process = pb.start();
                process.waitFor();
            }

            return true;
        } catch (Exception e) {
            logger.error("Error restoring data for user: {}", userId, e);
            return false;
        }
    }

    @Override
    public List<User> getAllUsers() {
        return userMapper.selectList(null);
    }

    @Override
    public Page<User> pageStudents(long pageNum, long pageSize,
                                   String schoolName,
                                   String collegeName,
                                   String majorName,
                                   String className,
                                   String grade,
                                   String studentNo,
                                   String realName) {
        Page<User> page = new Page<>(pageNum, pageSize);
        IPage<User> result = userMapper.pageStudents(page, schoolName, collegeName, majorName, className, grade, studentNo, realName);
        // MyBatis-Plus 会把记录与分页信息填充回 page
        page.setRecords(result.getRecords());
        page.setTotal(result.getTotal());
        page.setPages(result.getPages());
        return page;
    }

    @Override
    public Optional<User> findUserById(String userId) {
        return Optional.ofNullable(userMapper.selectById(userId));
    }

    @Override
    public User updateUser(User user) {
        if (userMapper.selectById(user.getUserId()) == null) {
            throw new RuntimeException("User not found: " + user.getUserId());
        }

        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        return user;
    }

    @Override
    public boolean deleteUser(String userId) {
        logger.info("Deleting user: {}", userId);
        deleteUserEnvironment(userId);
        User user = userMapper.selectById(userId);
        return user != null && userMapper.deleteById(userId) > 0;
    }

    @Override
    public boolean isUserEnvironmentExists(String userId) {
        return userEnvironmentMapper.selectByUserIdSingle(userId) != null;
    }

    @Override
    public UserEnvironment getAccessInfo(String userId) {
        return userEnvironmentMapper.selectByUserIdSingle(userId);
    }

    @Override
    public boolean resetUserPassword(String userId, String newPassword) {
        UserEnvironment environment = userEnvironmentMapper.selectByUserIdSingle(userId);
        if (environment == null) {
            return false;
        }

        try {
            // 由于已禁用密码认证，不再需要设置密码
            // 但保留方法以兼容现有接口
            environment.setIdePassword(""); // 设置为空字符串表示无密码
            logger.info("Password authentication disabled for user: {}, setting empty password", userId);
            
            // 重启IDE容器以确保配置生效（如果存在）
            if (containerExists("ide-" + userId)) {
                restartContainer("ide-" + userId);
                logger.info("IDE container restarted for user: {} to apply no-password config", userId);
            } else {
                logger.warn("IDE container does not exist for user: {}", userId);
            }
            return true;
        } catch (Exception e) {
            logger.error("Error resetting password for user: {}", userId, e);
            return false;
        }
    }

    @Override
    public boolean setUserResourceLimits(String userId, UserResource resourceLimits) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            return false;
        }

        // 资源限制现在通过UserResource表单独管理，不再存储在User表中
        userResourceMapper.updateById(resourceLimits);
        updateContainerResourceLimits(userId, resourceLimits);
        return true;
    }

    @Override
    public UserResource getSystemResourceUsage() {
        UserResource systemResource = new UserResource();
        systemResource.setUserId("system");

        double totalCpu = 0.0;
        double totalMemory = 0.0;
        double totalDisk = 0.0;
        int totalConnections = 0;

        for (UserResource resource : userResourceMapper.selectList(null)) {
            if (resource.getCurrentCpuUsage() != null) totalCpu += resource.getCurrentCpuUsage();
            if (resource.getCurrentMemoryUsage() != null) totalMemory += resource.getCurrentMemoryUsage();
            if (resource.getCurrentDiskUsage() != null) totalDisk += resource.getCurrentDiskUsage();
            if (resource.getCurrentNetworkUsage() != null) totalConnections += resource.getCurrentNetworkUsage();
        }

        systemResource.setCurrentCpuUsage((int) totalCpu);
        systemResource.setCurrentMemoryUsage((int) totalMemory);
        systemResource.setCurrentDiskUsage((int) totalCpu);
        systemResource.setCurrentNetworkUsage(totalConnections);

        return systemResource;
    }

    /**
     * 修复指定用户的容器权限
     */
    public boolean fixUserPermissions(String userId) {
        try {
            if (userEnvironmentMapper.selectByUserIdSingle(userId) == null) {
                logger.warn("User environment not found: {}", userId);
                return false;
            }

            // 修复容器权限
            fixContainerPermissions(userId);

            // 重启 IDE 容器以确保权限生效（如果存在）
            if (containerExists("ide-" + userId)) {
                try {
                    ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "restart", "ide-" + userId);
                    Process process = pb.start();
                    int exitCode = process.waitFor();
                    if (exitCode == 0) {
                        logger.info("IDE container restarted successfully for user: {}", userId);
                        // 等待容器完全启动
                        Thread.sleep(5000);
                    } else {
                        logger.warn("Failed to restart IDE container for user: {}", userId);
                    }
                } catch (Exception e) {
                    logger.warn("Error restarting IDE container for user: {}", userId, e);
                }
            } else {
                logger.info("IDE container does not exist for user: {}, skipping restart", userId);
            }

            return true;
        } catch (Exception e) {
            logger.error("Failed to fix permissions for user: {}", userId, e);
            return false;
        }
    }

    // 私有辅助方法

    private void initializeDirectories() {
        try {
            Files.createDirectories(Paths.get(USERS_DIR));
            Files.createDirectories(Paths.get(SHARED_DIR));
            Files.createDirectories(Paths.get("./backups"));
        } catch (IOException e) {
            logger.error("Failed to create directories", e);
        }
    }

    private UserResource getDefaultResourceLimits(String role) {
        UserResource limits = new UserResource();

        switch (role.toLowerCase()) {
            case "student":
                limits.setCpuLimit(1);
                limits.setMemoryLimit(2048);
                limits.setDiskLimit(10);
                limits.setNetworkLimit(10);
                break;
            case "teacher":
                limits.setCpuLimit(2);
                limits.setMemoryLimit(4096);
                limits.setDiskLimit(20);
                limits.setNetworkLimit(20);
                break;
            case "admin":
                limits.setCpuLimit(4);
                limits.setMemoryLimit(8192);
                limits.setDiskLimit(50);
                limits.setNetworkLimit(50);
                break;
            default:
                limits.setCpuLimit(1);
                limits.setMemoryLimit(2048);
                limits.setDiskLimit(10);
                limits.setNetworkLimit(10);
        }

        return limits;
    }

    // 端口分配与可用性校验改为使用 SharedContainerService

    private void releasePort(int port) {
        // 交由端口分配服务内部管理；此处保留空实现以兼容旧调用
    }


    private void createDockerEnvironment(String userId, Map<String, Integer> ports, Set<UserEnvironment.EnvironmentType> environmentTypes) throws Exception {
        logger.info("createDockerEnvironment开始执行，用户: {}, 端口: {}, 类型: {}", userId, ports, environmentTypes);
        String userDir = USERS_DIR + "/" + userId;
        Files.createDirectories(Paths.get(userDir));

        // 预拉取需要的镜像，特别是虚拟桌面镜像
//        prePullRequiredImages(environmentTypes);

        String dockerComposeContent = generateDockerCompose(userId, ports, environmentTypes);
        Files.write(Paths.get(userDir + "/docker-compose.yml"), dockerComposeContent.getBytes());

        // 使用重试逻辑执行docker compose up
        executeDockerComposeWithRetry(userDir, userId);

        // 等待容器启动
        Thread.sleep(5000);

        // 验证所有需要的服务是否已启动（例如 MYSQL/REDIS/等）
        if (!verifyServicesRunning(userId, ports)) {
            logger.error("Service verification failed after compose up for user {}.", userId);
            throw new RuntimeException("Some services are not running after compose up. See logs for details.");
        }

        // 修复权限问题（仅对IDE/Jupyter有效，其他服务忽略失败）
        fixContainerPermissions(userId);

        logger.info("createDockerEnvironment执行完成，用户: {}", userId);
    }

    private void prePullRequiredImages(Set<UserEnvironment.EnvironmentType> environmentTypes) {
        try {
            if (environmentTypes.contains(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP)) {
                logger.info("Pre-pulling virtual desktop image...");
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "pull", "accetto/ubuntu-vnc-xfce-firefox-g3:latest");
                pb.redirectErrorStream(true);
                Process process = pb.start();

                StringBuilder output = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        output.append(line).append("\n");
                    }
                }

                int exitCode = process.waitFor();
                if (exitCode != 0) {
                    logger.warn("Failed to pre-pull virtual desktop image: {}", output.toString());
                    // 不抛出异常，让docker compose尝试拉取
                } else {
                    logger.info("Successfully pre-pulled virtual desktop image");
                }
            }
        } catch (Exception e) {
            logger.warn("Error during image pre-pull: {}", e.getMessage());
            // 不抛出异常，让docker compose尝试拉取
        }
    }

    private void executeDockerComposeWithRetry(String userDir, String userId) throws Exception {
        int maxRetries = 3;
        int retryCount = 0;
        Exception lastException = null;

        while (retryCount < maxRetries) {
            try {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "compose", "up", "-d");
                pb.directory(new File(userDir));
                pb.redirectErrorStream(true);
                Process process = pb.start();

                // 读取输出
                StringBuilder output = new StringBuilder();
                boolean processCompleted = false;

                // 使用线程来读取输出，避免阻塞
                Thread outputReader = new Thread(() -> {
                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            output.append(line).append("\n");
                        }
                    } catch (IOException e) {
                        logger.warn("Error reading process output: {}", e.getMessage());
                    }
                });
                outputReader.start();

                // 等待进程完成，设置超时时间（5分钟）
                boolean completed = process.waitFor(5, TimeUnit.MINUTES);
                if (!completed) {
                    logger.warn("Docker compose up timeout for user {} (attempt {})", userId, retryCount + 1);
                    process.destroyForcibly();
                    lastException = new RuntimeException("Docker compose up timeout after 5 minutes");
                } else {
                    outputReader.join(10000); // 等待输出读取线程完成
                    processCompleted = true;

                    int exitCode = process.exitValue();
                    if (exitCode == 0) {
                        logger.info("Docker compose up successful for user {} (attempt {})", userId, retryCount + 1);
                        return;
                    } else {
                        logger.warn("Docker compose failed for user {} (attempt {}): {}", userId, retryCount + 1, output.toString());
                        lastException = new RuntimeException("Docker compose failed: " + output.toString());
                    }
                }
            } catch (Exception e) {
                logger.warn("Exception during docker compose up for user {} (attempt {}): {}", userId, retryCount + 1, e.getMessage());
                lastException = e;
            }

            retryCount++;
            if (retryCount < maxRetries) {
                logger.info("Retrying docker compose up for user {} (attempt {}/{})", userId, retryCount + 1, maxRetries);
                Thread.sleep(2000 * retryCount); // 递增延迟
            }
        }

        // 所有重试都失败了
        logger.error("All {} attempts to run docker compose up failed for user {}", maxRetries, userId);
        throw lastException != null ? lastException : new RuntimeException("Failed to create Docker environment after " + maxRetries + " attempts");
    }


    private String generateDockerCompose(String userId, Map<String, Integer> ports, Set<UserEnvironment.EnvironmentType> environmentTypes) {
        StringBuilder compose = new StringBuilder();
        compose.append("services:\n");

        // IDE服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.IDE)) {
            compose.append("  ide-").append(userId).append(":\n");
            compose.append("    image: crpi-ycy45s92ut53ifd4.cn-hangzhou.personal.cr.aliyuncs.com/boogie0095/code-server-rebuild:latest\n");
            compose.append("    container_name: ide-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("ide")).append(":8080\"\n");
            compose.append("    environment:\n");
            compose.append("      # 禁用密码认证，实现无密码登录\n");
            compose.append("      PASSWORD: \"\"\n");
            compose.append("      AUTH: none\n");
            compose.append("      # 禁用认证相关配置\n");
            compose.append("      DISABLE_AUTH: true\n");
            compose.append("      SKIP_AUTH: true\n");
            compose.append("      # 用户标识\n");
            compose.append("      USER_ID: ").append(userId).append("\n");
            compose.append("      # 剪贴板权限配置\n");
            compose.append("      ENABLE_CLIPBOARD: true\n");
            compose.append("      CLIPBOARD_READ: true\n");
            compose.append("      CLIPBOARD_WRITE: true\n");
            compose.append("      # 浏览器权限配置\n");
            compose.append("      BROWSER_PERMISSIONS: clipboard-read,clipboard-write\n");
            compose.append("      # 安全配置\n");
            compose.append("      SECURITY_ALLOW_INSECURE_LOCALHOST: true\n");
            compose.append("      SECURITY_ALLOW_HTTP: true\n");
            compose.append("      # 额外权限配置\n");
            compose.append("      EXTENSIONS_GALLERY: '{\"serviceUrl\":\"https://marketplace.visualstudio.com/_apis/public/gallery\",\"cacheUrl\":\"https://vscode.cdn.azure.cn\",\"itemUrl\":\"https://marketplace.visualstudio.com/items\"}'\n");
            compose.append("    # 强制覆盖启动命令，确保无密码启动\n");
            compose.append("    command: [\"/usr/bin/entrypoint.sh\", \"code-server\", \"--bind-addr\", \"0.0.0.0:8080\", \"--auth\", \"none\", \"/home/coder/project\"]\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-data:/home/coder/project\n");
            compose.append("      - user-").append(userId).append("-config:/home/coder/.config\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '2'\n");
            compose.append("          memory: 4G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.5'\n");
            compose.append("          memory: 1G\n");
            compose.append("    restart: unless-stopped\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // Jupyter服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.JUPYTER)) {
            compose.append("  jupyter-").append(userId).append(":\n");
//            compose.append("    image: jupyter/datascience-notebook:latest\n");
            compose.append("    image: jupyter/minimal-notebook:latest\n");
            compose.append("    container_name: jupyter-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("jupyter")).append(":8888\"\n");
            compose.append("    environment:\n");
            compose.append("      JUPYTER_ENABLE_LAB: yes\n");
            compose.append("      # 禁用Jupyter认证，实现无密码登录\n");
            compose.append("      JUPYTER_TOKEN: \"\"\n");
            compose.append("      JUPYTER_PASSWORD: \"\"\n");
            compose.append("      # 禁用密码和token认证\n");
            compose.append("      JUPYTER_DISABLE_AUTH: true\n");
            compose.append("      # 禁用安全模式\n");
            compose.append("      JUPYTER_ALLOW_INSECURE_WRITES: true\n");
            compose.append("    # 强制覆盖启动命令，确保无密码启动\n");
            compose.append("    command: [\"start-notebook.sh\", \"--NotebookApp.token=''\", \"--NotebookApp.password=''\", \"--NotebookApp.disable_check_xsrf=True\", \"--NotebookApp.allow_origin='*'\"]\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-data:/home/jovyan/work\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '2'\n");
            compose.append("          memory: 4G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.5'\n");
            compose.append("          memory: 1G\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // 轻量级Jupyter服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.JUPYTER_LIGHT)) {
            compose.append("  jupyter-light-").append(userId).append(":\n");
            compose.append("    image: jupyter/minimal-notebook:latest\n");
            compose.append("    container_name: jupyter-light-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("jupyter")).append(":8888\"\n");
            compose.append("    environment:\n");
            compose.append("      JUPYTER_ENABLE_LAB: yes\n");
            compose.append("      # 禁用Jupyter认证，实现无密码登录\n");
            compose.append("      JUPYTER_TOKEN: \"\"\n");
            compose.append("      JUPYTER_PASSWORD: \"\"\n");
            compose.append("      # 禁用密码和token认证\n");
            compose.append("      JUPYTER_DISABLE_AUTH: true\n");
            compose.append("      # 轻量级配置\n");
            compose.append("      JUPYTER_CONFIG_DIR: /home/jovyan/.jupyter\n");
            compose.append("      # 禁用不必要的扩展和功能\n");
            compose.append("      JUPYTER_DISABLE_EXTENSIONS: yes\n");
            compose.append("      # 设置工作目录\n");
            compose.append("      WORK_DIR: /home/jovyan/work\n");
            compose.append("      # 内存优化\n");
            compose.append("      JUPYTER_MEMORY_LIMIT: 1G\n");
            compose.append("      # 禁用自动保存以减少磁盘I/O\n");
            compose.append("      JUPYTER_AUTOSAVE_INTERVAL: 0\n");
            compose.append("      # 禁用安全模式\n");
            compose.append("      JUPYTER_ALLOW_INSECURE_WRITES: true\n");
            compose.append("    # 强制覆盖启动命令，确保无密码启动\n");
            compose.append("    command: [\"start-notebook.sh\", \"--NotebookApp.token=''\", \"--NotebookApp.password=''\", \"--NotebookApp.disable_check_xsrf=True\", \"--NotebookApp.allow_origin='*'\"]\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-data:/home/jovyan/work\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '1'\n");
            compose.append("          memory: 2G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.25'\n");
            compose.append("          memory: 512M\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // 虚拟桌面服务
//        if (environmentTypes.contains(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP)) {
//            compose.append("  virtual-desktop-").append(userId).append(":\n");
//            compose.append("    image: accetto/ubuntu-vnc-xfce-firefox-g3:latest\n");
//            compose.append("    container_name: virtual-desktop-").append(userId).append("\n");
//            compose.append("    ports:\n");
//            compose.append("      - \"").append(ports.get("virtual-desktop")).append(":6901\"\n");
//            compose.append("    environment:\n");
//            compose.append("      # 禁用VNC密码验证，实现直接登录\n");
//            compose.append("      VNC_PW: \"\"\n");
//            compose.append("      VNC_VIEW_ONLY: false\n");
//            compose.append("      VNC_RESOLUTION: 1920x1080\n");
//            compose.append("      VNC_COL_DEPTH: 24\n");
//            compose.append("      USER_ID: ").append(userId).append("\n");
//            compose.append("      # 自动登录配置\n");
//            compose.append("      AUTOLOGIN: true\n");
//            compose.append("      SKIP_VNC_PASSWORD: true\n");
//            compose.append("      NO_VNC_PASSWORD: true\n");
//            compose.append("      # 语言和字体配置\n");
//            compose.append("      LANG: zh_CN.UTF-8\n");
//            compose.append("      LC_ALL: zh_CN.UTF-8\n");
//            compose.append("      LANGUAGE: zh_CN:zh\n");
//            compose.append("      DEBIAN_FRONTEND: noninteractive\n");
//            compose.append("      # 桌面自动启动配置\n");
//            compose.append("      DESKTOP_AUTOSTART: true\n");
//            compose.append("      XFCE_AUTOSTART: true\n");
//            compose.append("    volumes:\n");
//            compose.append("      - user-").append(userId).append("-desktop-data:/home/headless/Desktop\n");
//            compose.append("      - user-").append(userId).append("-data:/home/headless/shared\n");
//            compose.append("    deploy:\n");
//            compose.append("      resources:\n");
//            compose.append("        limits:\n");
//            compose.append("          cpus: '2'\n");
//            compose.append("          memory: 4G\n");
//            compose.append("        reservations:\n");
//            compose.append("          cpus: '0.5'\n");
//            compose.append("          memory: 1G\n");
//            compose.append("    user: root\n");
//            compose.append("    command: >\n");
//            compose.append("      bash -c \"\n");
//            compose.append("        # 系统初始化\n");
//            compose.append("        apt-get update -y &&\n");
//            compose.append("        apt-get install -y locales &&\n");
//            compose.append("        locale-gen zh_CN.UTF-8 &&\n");
//            compose.append("        apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk &&\n");
//            compose.append("        fc-cache -fv &&\n");
//            compose.append("        # 配置自动登录\n");
//            compose.append("        mkdir -p /home/headless/.vnc &&\n");
//            compose.append("        echo '' > /home/headless/.vnc/passwd &&\n");
//            compose.append("        chmod 600 /home/headless/.vnc/passwd &&\n");
//            compose.append("        # 配置VNC服务器无密码启动\n");
//            compose.append("        sed -i 's/^.*authentication.*$/authentication=none/' /etc/vnc/xstartup 2>/dev/null || true &&\n");
//            compose.append("        # 设置文件权限\n");
//            compose.append("        chown -R headless:headless /home/headless &&\n");
//            compose.append("        # 启动VNC服务，禁用密码验证\n");
//            compose.append("        export VNC_PW='' &&\n");
//            compose.append("        export SKIP_VNC_PASSWORD=true &&\n");
//            compose.append("        exec /usr/bin/tini -- /dockerstartup/vnc_startup.sh --skip-vnc-password --no-password\n");
//            compose.append("      \"\n");
//            compose.append("    networks:\n");
//            compose.append("      - user-").append(userId).append("-network\n\n");
//        }



                   if (environmentTypes.contains(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP)) {
               compose.append("  virtual-desktop-").append(userId).append(":\n");
               compose.append("    image: dorowu/ubuntu-desktop-lxde-vnc:latest\n");
               compose.append("    container_name: virtual-desktop-").append(userId).append("\n");
               compose.append("    ports:\n");
               compose.append("      - \"").append(ports.get("virtual-desktop")).append(":80\"\n");
               compose.append("    environment:\n");
               compose.append("      # 设置VNC密码\n");
               compose.append("      VNC_PW: \"123456\"\n");
               compose.append("      VNC_PASSWORD: \"123456\"\n");
               compose.append("      VNC_NO_PASSWORD: \"0\"\n");
               compose.append("      VNC_DISABLE_AUTH: \"0\"\n");
               compose.append("      # 显示配置\n");
               compose.append("      RESOLUTION: 1920x1080\n");
               compose.append("      # 用户配置\n");
               compose.append("      USER: 1000\n");
                                       compose.append("      # 语言配置\n");
                        compose.append("      LANG: zh_CN.UTF-8\n");
                        compose.append("      LC_ALL: zh_CN.UTF-8\n");
                                                compose.append("      LANGUAGE: zh_CN:zh\n");
                        compose.append("      TZ: Asia/Shanghai\n");
                        compose.append("    command: [\"/dockerstartup/vnc_startup.rc\"]\n");
                        compose.append("    volumes:\n");
                        compose.append("      - user-").append(userId).append("-data:/home/accetto/shared\n");
                        compose.append("      - user-").append(userId).append("-desktop-data:/home/accetto/Desktop\n");
                        compose.append("    deploy:\n");
               compose.append("      resources:\n");
               compose.append("        limits:\n");
               compose.append("          cpus: '1'\n");
               compose.append("          memory: 2G\n");
               compose.append("        reservations:\n");
               compose.append("          cpus: '0.25'\n");
               compose.append("          memory: 512M\n");
               compose.append("    networks:\n");
               compose.append("      - user-").append(userId).append("-network\n\n");
           }

        // Redis服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.REDIS)) {
            compose.append("  redis-").append(userId).append(":\n");
            compose.append("    image: redis:7-alpine\n");
            compose.append("    container_name: redis-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("redis")).append(":6379\"\n");
            compose.append("    command: redis-server --requirepass 123456\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-redis-data:/data\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '1'\n");
            compose.append("          memory: 1G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.2'\n");
            compose.append("          memory: 256M\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // MySQL服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.MYSQL)) {
            // 生成短用户名（MySQL 限制 32 字符）
            String shortUserId = userId.length() > 20 ? userId.substring(0, 20) : userId;
            String mysqlUser = "u" + shortUserId.replaceAll("[^a-zA-Z0-9]", "");
            if (mysqlUser.length() > 30) {
                mysqlUser = mysqlUser.substring(0, 30);
            }

            compose.append("  mysql-").append(userId).append(":\n");
            compose.append("    image: mysql:5.7\n");
            compose.append("    container_name: mysql-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("mysql")).append(":3306\"\n");
            compose.append("    environment:\n");
            compose.append("      MYSQL_ROOT_PASSWORD: 123456\n");
            compose.append("      MYSQL_DATABASE: ").append(mysqlUser).append("_db\n");
            compose.append("      MYSQL_USER: ").append(mysqlUser).append("\n");
            compose.append("      MYSQL_PASSWORD: 123456\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-mysql-data:/var/lib/mysql\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '1'\n");
            compose.append("          memory: 2G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.2'\n");
            compose.append("          memory: 512M\n");
            compose.append("    command: --default-authentication-plugin=mysql_native_password\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // RabbitMQ服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.RABBITMQ)) {
            compose.append("  rabbitmq-").append(userId).append(":\n");
            compose.append("    image: rabbitmq:3-management\n");
            compose.append("    container_name: rabbitmq-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("rabbitmq")).append(":5672\"\n");
            compose.append("      - \"").append(ports.get("rabbitmq") + 1).append(":15672\"\n");
            compose.append("    environment:\n");
            compose.append("      RABBITMQ_DEFAULT_USER: user_").append(userId).append("\n");
            compose.append("      RABBITMQ_DEFAULT_PASS: 123456\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-rabbitmq-data:/var/lib/rabbitmq\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '1'\n");
            compose.append("          memory: 1G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.2'\n");
            compose.append("          memory: 256M\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // Elasticsearch服务
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.ELASTICSEARCH)) {
            compose.append("  elasticsearch-").append(userId).append(":\n");
            compose.append("    image: elasticsearch:8.11.0\n");
            compose.append("    container_name: elasticsearch-").append(userId).append("\n");
            compose.append("    ports:\n");
            compose.append("      - \"").append(ports.get("elasticsearch")).append(":9200\"\n");
            compose.append("      - \"").append(ports.get("elasticsearch") + 1).append(":9300\"\n");
            compose.append("    environment:\n");
            compose.append("      - discovery.type=single-node\n");
            compose.append("      - xpack.security.enabled=false\n");
            compose.append("      - \"ES_JAVA_OPTS=-Xms512m -Xmx512m\"\n");
            compose.append("    volumes:\n");
            compose.append("      - user-").append(userId).append("-elasticsearch-data:/usr/share/elasticsearch/data\n");
            compose.append("    deploy:\n");
            compose.append("      resources:\n");
            compose.append("        limits:\n");
            compose.append("          cpus: '1'\n");
            compose.append("          memory: 2G\n");
            compose.append("        reservations:\n");
            compose.append("          cpus: '0.2'\n");
            compose.append("          memory: 512M\n");
            compose.append("    networks:\n");
            compose.append("      - user-").append(userId).append("-network\n\n");
        }

        // Volumes
        compose.append("volumes:\n");
        compose.append("  user-").append(userId).append("-data:\n");
        compose.append("    driver: local\n");

        // 添加 IDE 配置卷定义
        if (environmentTypes.contains(UserEnvironment.EnvironmentType.IDE)) {
            compose.append("  user-").append(userId).append("-config:\n");
            compose.append("    driver: local\n");
        }

        if (environmentTypes.contains(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP)) {
            compose.append("  user-").append(userId).append("-desktop-data:\n");
            compose.append("    driver: local\n");
        }

        if (environmentTypes.contains(UserEnvironment.EnvironmentType.REDIS)) {
            compose.append("  user-").append(userId).append("-redis-data:\n");
            compose.append("    driver: local\n");
        }

        if (environmentTypes.contains(UserEnvironment.EnvironmentType.MYSQL)) {
            compose.append("  user-").append(userId).append("-mysql-data:\n");
            compose.append("    driver: local\n");
        }

        if (environmentTypes.contains(UserEnvironment.EnvironmentType.RABBITMQ)) {
            compose.append("  user-").append(userId).append("-rabbitmq-data:\n");
            compose.append("    driver: local\n");
        }

        if (environmentTypes.contains(UserEnvironment.EnvironmentType.ELASTICSEARCH)) {
            compose.append("  user-").append(userId).append("-elasticsearch-data:\n");
            compose.append("    driver: local\n");
        }

        // Networks
        compose.append("networks:\n");
        compose.append("  user-").append(userId).append("-network:\n");
        compose.append("    driver: bridge\n");

        return compose.toString();
    }

    private void createDatabaseUser(String userId) {
        try {
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "practice-platform-postgres-1",
                    "psql", "-U", "platform_user", "-d", "practice_platform", "-c",
                    String.format("CREATE USER user_%s WITH PASSWORD 'user_%s_pass'; CREATE DATABASE user_%s_db OWNER user_%s;", userId, userId, userId, userId)
            );
            Process process = pb.start();
            process.waitFor();
        } catch (Exception e) {
            logger.error("Failed to create database user: {}", userId, e);
        }
    }

    private void deleteDatabaseUser(String userId) {
        try {
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "practice-platform-postgres-1",
                    "psql", "-U", "platform_user", "-d", "practice_platform", "-c",
                    String.format("DROP DATABASE IF EXISTS user_%s_db; DROP USER IF EXISTS user_%s;", userId, userId)
            );
            Process process = pb.start();
            process.waitFor();
        } catch (Exception e) {
            logger.error("Failed to delete database user: {}", userId, e);
        }
    }

    private void deleteDirectory(File dir) {
        if (dir.exists()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDirectory(file);
                    } else {
                        file.delete();
                    }
                }
            }
            dir.delete();
        }
    }

    private void restartContainer(String containerName) throws Exception {
        ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "restart", containerName);
        Process process = pb.start();
        process.waitFor();
    }

    private void updateContainerResourceLimits(String userId, UserResource limits) {
        try {
            // 只更新实际存在的容器
            if (containerExists("ide-" + userId)) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "update",
                        "--cpus", String.valueOf(limits.getCpuLimit()),
                        "--memory", limits.getMemoryLimit() + "m",
                        "ide-" + userId
                );
                Process process = pb.start();
                process.waitFor();
                logger.info("Updated resource limits for IDE container: {}", userId);
            }

            if (containerExists("jupyter-" + userId)) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "update",
                        "--cpus", String.valueOf(limits.getCpuLimit()),
                        "--memory", limits.getMemoryLimit() + "m",
                        "jupyter-" + userId
                );
                Process process = pb.start();
                process.waitFor();
                logger.info("Updated resource limits for Jupyter container: {}", userId);
            }

            if (containerExists("jupyter-light-" + userId)) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "update",
                        "--cpus", String.valueOf(limits.getCpuLimit()),
                        "--memory", limits.getMemoryLimit() + "m",
                        "jupyter-light-" + userId
                );
                Process process = pb.start();
                process.waitFor();
                logger.info("Updated resource limits for Jupyter Light container: {}", userId);
            }
        } catch (Exception e) {
            logger.error("Failed to update container resource limits for user: {}", userId, e);
        }
    }

    private void fixContainerPermissions(String userId) {
        try {
            logger.info("Fixing permissions for user: {}", userId);

            // 检查并修复 IDE 容器权限（如果存在）
            if (containerExists("ide-" + userId)) {
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "-u", "root", "ide-" + userId,
                        "chown", "-R", "coder:coder", "/home/coder/project");
                Process process = pb.start();
                int exitCode = process.waitFor();
                if (exitCode != 0) {
                    logger.warn("IDE permission fix failed with exit code: {}", exitCode);
                } else {
                    logger.info("IDE permissions fixed successfully");
                }

                // 设置目录权限为 755
                pb = new ProcessBuilder("/usr/bin/docker", "exec", "-u", "root", "ide-" + userId,
                        "chmod", "-R", "755", "/home/coder/project");
                process = pb.start();
                exitCode = process.waitFor();
                if (exitCode != 0) {
                    logger.warn("IDE directory permissions fix failed with exit code: {}", exitCode);
                }
            }

            // 检查并修复 Jupyter 容器权限（如果存在）
            if (containerExists("jupyter-" + userId) || containerExists("jupyter-light-" + userId)) {
                String containerName = containerExists("jupyter-light-" + userId) ? "jupyter-light-" + userId : "jupyter-" + userId;
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "-u", "root", containerName,
                        "chown", "-R", "jovyan:users", "/home/jovyan/work");
                Process process = pb.start();
                int exitCode = process.waitFor();
                if (exitCode != 0) {
                    logger.warn("Jupyter permission fix failed with exit code: {}", exitCode);
                } else {
                    logger.info("Jupyter permissions fixed successfully");
                }

                // 设置目录权限为 755
                pb = new ProcessBuilder("/usr/bin/docker", "exec", "-u", "root", containerName,
                        "chmod", "-R", "755", "/home/jovyan/work");
                process = pb.start();
                exitCode = process.waitFor();
                if (exitCode != 0) {
                    logger.warn("Jupyter directory permissions fix failed with exit code: {}", exitCode);
                }
            }

            logger.info("Permissions fixed for user: {}", userId);
        } catch (Exception e) {
            logger.error("Failed to fix permissions for user: {}", userId, e);
        }
    }

    /**
     * 检查容器是否存在
     */
    private boolean containerExists(String containerName) {
        try {
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "ps", "-a", "--format", "{{.Names}}");
            Process process = pb.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.equals(containerName)) {
                        return true;
                    }
                }
            }
            process.waitFor();
        } catch (Exception e) {
            logger.warn("Failed to check if container exists: {}", containerName, e);
        }
        return false;
    }

    /**
     * 为用户生成统一的 IDE 密码
     */
    private String generateUserPassword(String userId) {
        // 已禁用密码认证，返回空字符串表示无密码
        return "";
    }

    /**
     * 为用户生成统一的 Jupyter Token
     */
    private String generateJupyterToken(String userId) {
        // 已禁用token认证，返回空字符串表示无token
        return "";
    }

    private String generateRedisPassword(String userId) {
        return "123456";
    }

    private String generateMysqlPassword(String userId) {
        return "123456";
    }

    private String generateRabbitmqPassword(String userId) {
        return "123456";
    }

    private String generateElasticsearchPassword(String userId) {
        return "123456";
    }

    /**
     * 验证容器状态
     */
    private boolean verifyContainerStatus(String userId) {
        try {
            // 检查容器是否运行且有端口映射
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "ps", "--format", "{{.Names}}\t{{.Status}}\t{{.Ports}}");
            Process process = pb.start();

            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.contains("ide-" + userId)) {
                        // 检查容器状态
                        if (line.contains("Up") && line.contains("->")) {
                            logger.info("Container verification successful for user: {}", userId);
                            return true;
                        } else {
                            logger.warn("Container exists but not properly running for user: {}", userId);
                            return false;
                        }
                    }
                }
            }

            process.waitFor();
            logger.warn("Container not found for user: {}", userId);
            return false;
        } catch (Exception e) {
            logger.error("Error verifying container status for user: {}", userId, e);
            return false;
        }
    }

    /**
     * 验证根据端口表应存在的服务容器是否已运行且端口映射生效
     */
    private boolean verifyServicesRunning(String userId, Map<String, Integer> ports) {
        try {
            logger.info("开始验证服务状态，用户: {}, 端口: {}", userId, ports);
            final int maxAttempts = 60; // 增加到120秒，给MySQL等数据库服务更多启动时间
            for (int attempt = 1; attempt <= maxAttempts; attempt++) {
                logger.info("执行docker ps命令，尝试: {}/{}", attempt, maxAttempts);
                ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "ps", "--format", "{{.Names}}\t{{.Ports}}\t{{.Status}}");
                Process process = pb.start();
                java.util.List<String> lines = new java.util.ArrayList<>();
                java.util.List<String> errorLines = new java.util.ArrayList<>();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                     BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        lines.add(line);
                    }
                    while ((line = errorReader.readLine()) != null) {
                        errorLines.add(line);
                    }
                }
                process.waitFor();

                logger.info("docker ps命令执行完成，退出码: {}, 输出行数: {}, 错误行数: {}", process.exitValue(), lines.size(), errorLines.size());
                if (!errorLines.isEmpty()) {
                    logger.warn("docker ps命令错误输出:");
                    for (String errorLine : errorLines) {
                        logger.warn("  {}", errorLine);
                    }
                }

                // 记录当前Docker容器状态，用于调试
                if (attempt == 1 || attempt % 10 == 0) {
                    logger.info("当前Docker容器状态 (尝试 {}/{}):", attempt, maxAttempts);
                    for (String line : lines) {
                        logger.info("  {}", line);
                    }
                }

                // 记录要查找的容器名称，用于调试
                logger.info("正在查找的容器名称:");
                if (ports.containsKey("ide")) logger.info("  IDE: ide-{}", userId);
                if (ports.containsKey("jupyter")) logger.info("  Jupyter: jupyter-{}", userId);
                if (ports.containsKey("redis")) logger.info("  Redis: redis-{}", userId);
                if (ports.containsKey("mysql")) logger.info("  MySQL: mysql-{}", userId);
                if (ports.containsKey("rabbitmq")) logger.info("  RabbitMQ: rabbitmq-{}", userId);
                if (ports.containsKey("elasticsearch")) logger.info("  Elasticsearch: elasticsearch-{}", userId);
                if (ports.containsKey("virtual-desktop")) logger.info("  虚拟桌面: virtual-desktop-{}", userId);

                boolean ok = true;
                if (ports.containsKey("ide") && lines.stream().noneMatch(l -> l.contains("ide-" + userId) && l.contains("Up"))) {
                    logger.debug("IDE服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }
                if (ports.containsKey("jupyter") && lines.stream().noneMatch(l -> l.contains("jupyter-" + userId) && l.contains("Up"))) {
                    logger.debug("Jupyter服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }
                if (ports.containsKey("redis") && lines.stream().noneMatch(l -> l.contains("redis-" + userId) && l.contains("Up"))) {
                    logger.debug("Redis服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }
                if (ports.containsKey("mysql") && lines.stream().noneMatch(l -> l.contains("mysql-" + userId) && (l.contains("Up") || l.contains("starting") || l.contains("healthy") || l.contains("running")))) {
                    logger.debug("MySQL服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    // 检查是否有MySQL容器存在，即使状态不是Up
                    boolean mysqlContainerExists = lines.stream().anyMatch(l -> l.contains("mysql-" + userId));
                    if (mysqlContainerExists) {
                        // 记录容器的实际状态
                        String containerStatus = lines.stream()
                                .filter(l -> l.contains("mysql-" + userId))
                                .findFirst()
                                .orElse("未找到状态信息");
                        logger.info("MySQL容器存在但状态不正确，状态: {}, 尝试: {}/{}", containerStatus, attempt, maxAttempts);
                    } else {
                        logger.info("MySQL容器不存在，尝试: {}/{}", attempt, maxAttempts);
                    }
                    ok = false;
                }
                if (ports.containsKey("rabbitmq") && lines.stream().noneMatch(l -> l.contains("rabbitmq-" + userId) && l.contains("Up"))) {
                    logger.debug("RabbitMQ服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }
                if (ports.containsKey("elasticsearch") && lines.stream().noneMatch(l -> l.contains("elasticsearch-" + userId) && l.contains("Up"))) {
                    logger.debug("Elasticsearch服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }
                if (ports.containsKey("virtual-desktop") && lines.stream().noneMatch(l -> l.contains("virtual-desktop-" + userId) && l.contains("Up"))) {
                    logger.debug("虚拟桌面服务未就绪，尝试: {}/{}", attempt, maxAttempts);
                    ok = false;
                }

                if (ok) {
                    logger.info("所有服务验证成功，用户: {}", userId);
                    return true;
                }

                // 每10次尝试记录一次日志
                if (attempt % 10 == 0) {
                    logger.info("服务验证中，用户: {}, 尝试: {}/{}", userId, attempt, maxAttempts);
                }

                // 未全部就绪，等待后重试
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ie) { /* ignore */ }
            }
            logger.warn("服务验证超时，用户: {}, 等待了{}秒", userId, maxAttempts * 2);
            return false;
        } catch (Exception e) {
            logger.error("Error verifying services for user {}: {}", userId, e.getMessage());
            return false;
        }
    }

    private void allocateMissingPorts(java.util.Set<UserEnvironment.EnvironmentType> types, java.util.Map<String, Integer> ports) {
        if (types.contains(UserEnvironment.EnvironmentType.IDE) && !ports.containsKey("ide"))
            ports.put("ide", sharedContainerService.allocatePort());
        if ((types.contains(UserEnvironment.EnvironmentType.JUPYTER) || types.contains(UserEnvironment.EnvironmentType.JUPYTER_LIGHT)) && !ports.containsKey("jupyter"))
            ports.put("jupyter", sharedContainerService.allocatePort());
        if (types.contains(UserEnvironment.EnvironmentType.REDIS) && !ports.containsKey("redis"))
            ports.put("redis", sharedContainerService.allocatePort());
        if (types.contains(UserEnvironment.EnvironmentType.MYSQL) && !ports.containsKey("mysql"))
            ports.put("mysql", sharedContainerService.allocatePort());
        if (types.contains(UserEnvironment.EnvironmentType.RABBITMQ) && !ports.containsKey("rabbitmq"))
            ports.put("rabbitmq", sharedContainerService.allocatePort());
        if (types.contains(UserEnvironment.EnvironmentType.ELASTICSEARCH) && !ports.containsKey("elasticsearch"))
            ports.put("elasticsearch", sharedContainerService.allocatePort());
        if (types.contains(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP) && !ports.containsKey("virtual-desktop"))
            ports.put("virtual-desktop", sharedContainerService.allocatePort());
    }

    private void setUrlsFromPorts(UserEnvironment env, java.util.Map<String, Integer> ports) {
        if (ports.containsKey("ide")) env.setIdeUrl(BASE_URL + ":" + ports.get("ide"));
        if (ports.containsKey("jupyter")) env.setJupyterUrl(BASE_URL + ":" + ports.get("jupyter"));
        if (ports.containsKey("redis")) env.setRedisUrl(BASE_URL + ":" + ports.get("redis"));
        if (ports.containsKey("mysql")) env.setMysqlUrl(BASE_URL + ":" + ports.get("mysql"));
        if (ports.containsKey("rabbitmq")) env.setRabbitmqUrl(BASE_URL + ":" + ports.get("rabbitmq"));
        if (ports.containsKey("elasticsearch")) env.setElasticsearchUrl(BASE_URL + ":" + ports.get("elasticsearch"));
        if (ports.containsKey("virtual-desktop"))
            env.setVirtualDesktopUrl(BASE_URL + ":" + ports.get("virtual-desktop"));
    }

    private void updateEnvironmentRecordFromPorts(UserEnvironment env, java.util.Set<UserEnvironment.EnvironmentType> types, java.util.Map<String, Integer> ports) {
        env.setEnvironmentTypes(types);
        env.setIdePort(ports.getOrDefault("ide", -1));
        env.setJupyterPort(ports.getOrDefault("jupyter", -1));
        env.setRedisPort(ports.getOrDefault("redis", -1));
        env.setMysqlPort(ports.getOrDefault("mysql", -1));
        env.setRabbitmqPort(ports.getOrDefault("rabbitmq", -1));
        env.setElasticsearchPort(ports.getOrDefault("elasticsearch", -1));
        env.setVirtualDesktopPort(ports.getOrDefault("virtual-desktop", -1));
        setUrlsFromPorts(env, ports);
    }

    private java.util.Set<UserEnvironment.EnvironmentType> detectExistingServices(String userId) {
        java.util.Set<UserEnvironment.EnvironmentType> result = new java.util.HashSet<>();
        try {
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "ps", "-a", "--format", "{{.Names}}");
            Process process = pb.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String name;
                while ((name = reader.readLine()) != null) {
                    if (name.equals("ide-" + userId)) result.add(UserEnvironment.EnvironmentType.IDE);
                    if (name.equals("jupyter-" + userId)) result.add(UserEnvironment.EnvironmentType.JUPYTER);
                    if (name.equals("jupyter-light-" + userId))
                        result.add(UserEnvironment.EnvironmentType.JUPYTER_LIGHT);
                    if (name.equals("redis-" + userId)) result.add(UserEnvironment.EnvironmentType.REDIS);
                    if (name.equals("mysql-" + userId)) result.add(UserEnvironment.EnvironmentType.MYSQL);
                    if (name.equals("rabbitmq-" + userId)) result.add(UserEnvironment.EnvironmentType.RABBITMQ);
                    if (name.equals("elasticsearch-" + userId))
                        result.add(UserEnvironment.EnvironmentType.ELASTICSEARCH);
                    if (name.equals("virtual-desktop-" + userId))
                        result.add(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP);
                }
            }
            process.waitFor();
        } catch (Exception ignored) {
        }
        return result;
    }

    private void fillPortsFromDockerPs(String userId, java.util.Map<String, Integer> ports) {
        try {
            ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "ps", "--format", "{{.Names}}\t{{.Ports}}\t{{.Status}}");
            Process process = pb.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    if (line.startsWith("ide-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("ide", p);
                    } else if (line.startsWith("jupyter-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("jupyter", p);
                    } else if (line.startsWith("jupyter-light-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("jupyter", p);
                    } else if (line.startsWith("redis-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("redis", p);
                    } else if (line.startsWith("mysql-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("mysql", p);
                    } else if (line.startsWith("rabbitmq-" + userId + "\t")) {
                        // prefer management port if present
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("rabbitmq", p);
                    } else if (line.startsWith("elasticsearch-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("elasticsearch", p);
                    } else if (line.startsWith("virtual-desktop-" + userId + "\t")) {
                        Integer p = extractFirstHostPort(line);
                        if (p != null) ports.put("virtual-desktop", p);
                    }
                }
            }
            process.waitFor();
        } catch (Exception ignored) {
        }
    }

    private Integer extractFirstHostPort(String line) {
        // Example Ports field: "0.0.0.0:8444->8080/tcp, [::]:8444->8080/tcp"; extract first number before ->
        int arrow = line.indexOf("->");
        if (arrow == -1) return null;
        // backtrack to colon before host port
        int idx = arrow;
        while (idx > 0 && line.charAt(idx) != ':') idx--;
        if (idx <= 0) return null;
        int start = idx + 1;
        int end = start;
        while (end < line.length() && Character.isDigit(line.charAt(end))) end++;
        try {
            return Integer.parseInt(line.substring(start, end));
        } catch (Exception e) {
            return null;
        }
    }

    private void fillServicesFromCompose(String userId, java.util.Set<UserEnvironment.EnvironmentType> types, java.util.Map<String, Integer> ports) {
        java.nio.file.Path composePath = java.nio.file.Paths.get(USERS_DIR + "/" + userId + "/docker-compose.yml");
        if (!java.nio.file.Files.exists(composePath)) return;
        try {
            java.util.List<String> lines = java.nio.file.Files.readAllLines(composePath);
            String markerIde = "ide-" + userId + ":";
            String markerJ = "jupyter-" + userId + ":";
            String markerJL = "jupyter-light-" + userId + ":";
            String markerR = "redis-" + userId + ":";
            String markerM = "mysql-" + userId + ":";
            String markerQ = "rabbitmq-" + userId + ":";
            String markerE = "elasticsearch-" + userId + ":";
            String markerVD = "virtual-desktop-" + userId + ":";
            for (String l : lines) {
                if (l.contains(markerIde)) types.add(UserEnvironment.EnvironmentType.IDE);
                if (l.contains(markerJ)) types.add(UserEnvironment.EnvironmentType.JUPYTER);
                if (l.contains(markerJL)) types.add(UserEnvironment.EnvironmentType.JUPYTER_LIGHT);
                if (l.contains(markerR)) types.add(UserEnvironment.EnvironmentType.REDIS);
                if (l.contains(markerM)) types.add(UserEnvironment.EnvironmentType.MYSQL);
                if (l.contains(markerQ)) types.add(UserEnvironment.EnvironmentType.RABBITMQ);
                if (l.contains(markerE)) types.add(UserEnvironment.EnvironmentType.ELASTICSEARCH);
                if (l.contains(markerVD)) types.add(UserEnvironment.EnvironmentType.VIRTUAL_DESKTOP);
            }
        } catch (Exception ignored) {
        }
    }

    // 随机用户生成辅助方法
    private String generateRandomUserId() {
        Random random = new Random();
        int year = 2024 + random.nextInt(2); // 2024-2025
        int number = 1000 + random.nextInt(9000); // 1000-9999
        return year + String.format("%04d", number);
    }

    private String generateRandomUsername() {
        String[] prefixes = {"user", "student", "dev", "test", "demo", "temp"};
        String[] suffixes = {"001", "002", "003", "004", "005", "006", "007", "008", "009", "010"};
        Random random = new Random();

        String prefix = prefixes[random.nextInt(prefixes.length)];
        String suffix = suffixes[random.nextInt(suffixes.length)];
        int randomNum = 100 + random.nextInt(900); // 100-999

        return prefix + randomNum + suffix;
    }

    private String generateRandomEmail(String username) {
        String[] domains = {"example.com", "test.com", "demo.org", "practice.local", "dev.io"};
        Random random = new Random();
        String domain = domains[random.nextInt(domains.length)];

        return username + "@" + domain;
    }

    private String generateRandomPassword() {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder password = new StringBuilder();

        // 确保密码包含至少一个大写字母、一个小写字母和一个数字
        password.append((char) ('A' + random.nextInt(26))); // 大写字母
        password.append((char) ('a' + random.nextInt(26))); // 小写字母
        password.append((char) ('0' + random.nextInt(10))); // 数字

        // 添加剩余的随机字符
        for (int i = 3; i < 8; i++) {
            password.append(chars.charAt(random.nextInt(chars.length())));
        }

        // 打乱密码字符顺序
        char[] passwordArray = password.toString().toCharArray();
        for (int i = passwordArray.length - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            char temp = passwordArray[i];
            passwordArray[i] = passwordArray[j];
            passwordArray[j] = temp;
        }

        return new String(passwordArray);
    }

    @Override
    public List<SharedIDEEnvironment> getAllSharedIDEEnvironments() {
        try {
            return sharedIDEEnvironmentMapper.selectAllActive();
        } catch (Exception e) {
            logger.error("Failed to get all shared IDE environments", e);
            return new ArrayList<>();
        }
    }

    @Override
    public SharedIDEEnvironment getSharedIDEEnvironmentByGroupId(String groupId) {
        try {
            return sharedIDEEnvironmentMapper.selectByGroupId(groupId);
        } catch (Exception e) {
            logger.error("Failed to get shared IDE environment for group: {}", groupId, e);
            return null;
        }
    }

    @Override
    public java.util.Map<String, Object> createSharedMySQLEnvironment(String groupId, java.util.List<String> studentIds) {
        java.util.Objects.requireNonNull(groupId, "groupId");
        java.util.Objects.requireNonNull(studentIds, "studentIds");
        try {
            String groupDir = SHARED_DIR + "/" + groupId + "-mysql";
            java.nio.file.Files.createDirectories(java.nio.file.Paths.get(groupDir));

            // 统一使用一个对外端口
            int mysqlPort = sharedContainerService.allocatePort();

            // 生成 docker-compose.yml
            String compose = new StringBuilder()
                .append("services:\n")
                .append("  mysql-shared-").append(groupId).append(":\n")
                .append("    image: mysql:5.7\n")
                .append("    container_name: mysql-shared-").append(groupId).append("\n")
                .append("    ports:\n")
                .append("      - \"").append(mysqlPort).append(":3306\"\n")
                .append("    environment:\n")
                .append("      MYSQL_ROOT_PASSWORD: 123456\n")
                .append("    volumes:\n")
                .append("      - shared-").append(groupId).append("-mysql-data:/var/lib/mysql\n")
                .append("    command: --default-authentication-plugin=mysql_native_password\n")
                .append("    restart: unless-stopped\n")
                .append("    networks:\n")
                .append("      - shared-").append(groupId).append("-mysql-net\n\n")
                .append("volumes:\n")
                .append("  shared-").append(groupId).append("-mysql-data:\n")
                .append("    driver: local\n\n")
                .append("networks:\n")
                .append("  shared-").append(groupId).append("-mysql-net:\n")
                .append("    driver: bridge\n")
                .toString();

            java.nio.file.Files.write(java.nio.file.Paths.get(groupDir, "docker-compose.yml"), compose.getBytes());

            // 启动容器
            ProcessBuilder up = sharedContainerService.pbDocker("compose", "up", "-d");
            up.directory(new java.io.File(groupDir));
            Process upProc = up.start();
            upProc.waitFor();

            String containerName = "mysql-shared-" + groupId;

            // 等待 MySQL ready
            for (int i = 0; i < 60; i++) {
                try {
                    ProcessBuilder health = sharedContainerService.pbDocker(
                        "exec", containerName, "sh", "-lc",
                        "mysql -uroot -p123456 -e 'SELECT 1' 2>/dev/null"
                    );
                    Process hp = health.start();
                    int ex = hp.waitFor();
                    if (ex == 0) break;
                } catch (Exception ignore) {}
                Thread.sleep(2000);
            }

            // 为每个学生创建账户、数据库并授权仅访问自己的库
            java.util.Map<String, Object> userDbInfo = new java.util.HashMap<>();
            for (String sid : studentIds) {
                if (sid == null || sid.isBlank()) continue;
                String normalized = sid.replaceAll("[^a-zA-Z0-9]", "");
                String mysqlUser = ("u" + normalized);
                if (mysqlUser.length() > 30) mysqlUser = mysqlUser.substring(0, 30);
                String dbName = mysqlUser + "_db";
                String password = "123456"; // 可后续改为按需生成

                String sql = "mysql -uroot -p123456 -e \""
                    + "CREATE USER IF NOT EXISTS '" + mysqlUser + "'@'%' IDENTIFIED BY '" + password + "'; "
                    + "CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; "
                    + "ALTER DATABASE " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; "
                    + "GRANT ALL PRIVILEGES ON " + dbName + ".* TO '" + mysqlUser + "'@'%'; "
                    + "FLUSH PRIVILEGES;"
                    + "\"";

                ProcessBuilder exec = sharedContainerService.pbDocker("exec", containerName, "sh", "-lc", sql);
                Process ep = exec.start();
                int exit = ep.waitFor();
                if (exit != 0) {
                    String err = new String(ep.getErrorStream().readAllBytes());
                    throw new RuntimeException("Failed to provision MySQL for " + sid + ": " + err);
                }

                userDbInfo.put(sid, java.util.Map.of(
                    "username", mysqlUser,
                    "database", dbName,
                    "password", password
                ));
            }

            // 写入共享环境记录（复用 shared_ide_environment 表）
            java.util.Map<String, Integer> portMap = java.util.Map.of("mysql", mysqlPort);
            java.util.Map<String, String> urlMap = java.util.Map.of("mysql", BASE_URL + ":" + mysqlPort);

            SharedIDEEnvironment sharedEnv = new SharedIDEEnvironment(groupId, groupId + "-mysql", "admin");
            sharedEnv.setStudentIds(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(studentIds));
            sharedEnv.setPortMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(portMap));
            sharedEnv.setUrlMapping(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(urlMap));
            sharedEnv.setContainerName(containerName);
            sharedEnv.setStatus("RUNNING");
            sharedEnv.setLastStarted(java.time.LocalDateTime.now());
            sharedIDEEnvironmentMapper.insert(sharedEnv);

            java.util.Map<String, Object> res = new java.util.HashMap<>();
            res.put("success", true);
            res.put("groupId", groupId);
            res.put("mysqlPort", mysqlPort);
            res.put("mysqlUrl", BASE_URL + ":" + mysqlPort);
            res.put("userDbInfo", userDbInfo);
            res.put("environmentId", sharedEnv.getEnvironmentId());
            return res;
        } catch (Exception e) {
            logger.error("createSharedMySQLEnvironment failed", e);
            throw new RuntimeException("createSharedMySQLEnvironment failed: " + e.getMessage(), e);
        }
    }

    @Override
    public java.util.Map<String, Object> deleteSharedMySQLEnvironment(String groupId) {
        java.util.Objects.requireNonNull(groupId, "groupId");
        String groupDir = SHARED_DIR + "/" + groupId + "-mysql";
        try {
            ProcessBuilder down = sharedContainerService.pbDocker("compose", "down", "-v");
            down.directory(new java.io.File(groupDir));
            Process proc = down.start();
            proc.waitFor();

            deleteDirectory(new java.io.File(groupDir));

            SharedIDEEnvironment env = sharedIDEEnvironmentMapper.selectByGroupId(groupId);
            if (env != null && env.getContainerName() != null && env.getContainerName().startsWith("mysql-shared-")) {
                sharedIDEEnvironmentMapper.deleteById(env.getEnvironmentId());
            }

            java.util.Map<String, Object> res = new java.util.HashMap<>();
            res.put("success", true);
            res.put("message", "Shared MySQL environment deleted: " + groupId);
            return res;
        } catch (Exception e) {
            logger.error("deleteSharedMySQLEnvironment failed", e);
            throw new RuntimeException("deleteSharedMySQLEnvironment failed: " + e.getMessage(), e);
        }
    }

    private void ensureSharedLauncher(String containerName) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("cat >/usr/local/bin/start-code.sh <<'EOS'\n");
        sb.append("#!/bin/sh\n");
        sb.append("USER_NAME=\"$1\"\n");
        sb.append("PORT=\"$2\"\n");
        // TOKEN参数不再使用，确保完全无密码访问
        sb.append("CS=\"${4:-/usr/bin/code-server}\"\n");
        sb.append("export HOME=\"/home/$USER_NAME\"\n");
        sb.append("export XDG_CONFIG_HOME=\"$HOME/.config\"\n");
        sb.append("export XDG_DATA_HOME=\"$HOME/.local/share\"\n");
        sb.append("export USER=\"$USER_NAME\"\n");
        sb.append("export LOGNAME=\"$USER_NAME\"\n");
        // 完全禁用认证，确保无密码访问
        sb.append("export PASSWORD=\"\"\n");
        sb.append("export AUTH=none\n");
        sb.append("export DISABLE_AUTH=true\n");
        sb.append("export SKIP_AUTH=true\n");
        // 使常见 Python 发行版可用（conda/pyenv/本地 ~/.local/bin 等）
        sb.append("export PATH=\"/usr/local/bin:/usr/bin:$PATH\"\n");
        sb.append("if [ -d /opt/conda/bin ]; then export PATH=\"/opt/conda/bin:$PATH\"; fi\n");
        sb.append("if [ -d /home/coder/.local/bin ]; then export PATH=\"/home/coder/.local/bin:$PATH\"; fi\n");
        sb.append("if [ -x /home/coder/.pyenv/bin/pyenv ]; then PYENV_ROOT=\"/home/coder/.pyenv\"; export PYENV_ROOT; export PATH=\"$PYENV_ROOT/bin:$PATH\"; eval \"$($PYENV_ROOT/bin/pyenv init -)\"; fi\n");
        // 日志目录（写到学生可写的 Home 下）
        sb.append("mkdir -p \"$HOME/.local/share/logs\"\n");
        sb.append("LOG_PATH=\"$HOME/.local/share/logs/code-server.log\"\n");
        // 扩展安装（后台，不阻塞端口监听）
        sb.append("( \"$CS\" --list-extensions | grep -q '^ms-python.python$'    ) || (nohup \"$CS\" --install-extension ms-python.python       >$LOG_PATH 2>&1 & )\n");
        sb.append("( \"$CS\" --list-extensions | grep -q '^ms-python.vscode-pylance$' ) || (nohup \"$CS\" --install-extension ms-python.vscode-pylance >>$LOG_PATH 2>&1 & )\n");
        sb.append("( \"$CS\" --list-extensions | grep -q '^ms-toolsai.jupyter$' ) || (nohup \"$CS\" --install-extension ms-toolsai.jupyter    >>$LOG_PATH 2>&1 & )\n");
        // 启动并将输出重定向到每用户日志，完全禁用认证
        sb.append("exec \"$CS\" --bind-addr 0.0.0.0:\"$PORT\" --auth none \"/workspace/$USER_NAME\" >>$LOG_PATH 2>&1\n");
        sb.append("EOS\n");
        sb.append("chmod +x /usr/local/bin/start-code.sh\n");
        String launcher = sb.toString();

        ProcessBuilder pb = new ProcessBuilder("/usr/bin/docker", "exec", "-u", "0", containerName, "sh", "-lc", launcher);
        Process p = pb.start();
        int exit = p.waitFor();
        if (exit != 0) {
            throw new RuntimeException("Failed to install launcher in " + containerName);
        }
    }
}